﻿using Noria.Net.Chaos.Properties;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;

namespace Noria.Net.Chaos.Net
{
    class TamperRule : IRule
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private byte[] match;
        private byte[] replacement;

        public TamperRule(string match, string replacement)
        {
            this.match = MakeByteArray(match);
            this.replacement = MakeByteArray(replacement);

            log.InfoFormat("TamperRule was created, search '{0}' and replace to '{1}'", match, replacement);
        }

        internal static byte[] MakeByteArray(string text)
        {
            byte[] buff = new byte[text.Length / 3 + 1];
            var index = 0;
            foreach (var hex in text.Split(' '))
            {
                buff[index++] = byte.Parse(hex, NumberStyles.AllowHexSpecifier);
            }
            return buff;
        }

        public bool Apply(byte[] buffer, ref int length)
        {
            // input buffer is too short
            if (length < match.Length)
                return false;

            // no match
            int pos = (int)IndexOf(buffer, match);
            if (pos == -1)
                return false;

            // match!
            var increment = replacement.Length - match.Length;
            if (increment > buffer.Length - length)
            {
                // length exceeded!
                log.Warn("Pattern matched but buffer overflow, does nothing");
                return false;
            }

            // replace
            if (increment == 0)
            {
                // replace
                Array.Copy(replacement, 0, buffer, pos, replacement.Length);
            }
            else if (increment > 0)
            {
                // copy
                for (int i = length + increment - 1; i >= pos + match.Length; i--)
                {
                    buffer[i] = buffer[i - increment];
                }
                // replace
                Array.Copy(replacement, 0, buffer, pos, replacement.Length);
            }
            else
            {
                // replace
                Array.Copy(replacement, 0, buffer, pos, replacement.Length);
                // copy
                for (int i = pos + replacement.Length; i < length + increment; i++)
                {
                    buffer[i] = buffer[i - increment];
                }
            }

            log.InfoFormat("Pattern matched and replaced, input={0}, output={1}", length, length + increment);

            // change the ref variable 'length'
            length = length + increment;
            return true;
        }

        /// <summary>
        /// http://stackoverflow.com/questions/283456/byte-array-pattern-search
        /// </summary>
        internal static unsafe long IndexOf(byte[] source, byte[] pattern)
        {
            fixed (byte* H = source) fixed (byte* N = pattern)
            {
                long i = 0;
                for (byte* hNext = H, hEnd = H + source.LongLength; hNext < hEnd; i++, hNext++)
                {
                    bool found = true;
                    for (byte* hInc = hNext, nInc = N, nEnd = N + pattern.LongLength; found && nInc < nEnd; found = *nInc == *hInc, nInc++, hInc++) ;
                    if (found) return i;
                }
                return -1;
            }
        }
    }
}
